
import pyparsing

from pyparsing import Literal, CaselessKeyword, CaselessLiteral, \
     Word, Optional, OneOrMore, Group, Combine, ZeroOrMore, nums, \
     Forward, StringEnd, restOfLine, alphas, alphanums



class RegionFactorySkeleton(object):
    def create_shape(self, shape_name, number_list):
        return shape_name

    def op_or(self, region1, region2):
        return "(%s | %s)" % (region1, region2)

    def op_and(self, region1, region2):
        return "(%s & %s)" % (region1, region2)

    def op_sub(self, region1, region2):
        return "(%s - %s)" % (region1, region2)

    def op_not(self, region1):
        return "(! %s)" % (region1)




class RegionFactoryPlainList(object):

    def op_or(self, region1, region2):
        return region1 + region2

    def op_and(self, region1, region2):
        return region1 + region2

    def op_sub(self, region1, region2):
        return region1 + region2

    def op_not(self, region1):
        return region1


class RegionFactoryTupleList(RegionFactoryPlainList):
    def create_shape(self, shape_name, number_list):
        return [(shape_name, number_list)]


class RegionFactoryOpObject(object):
    def op_or(self, region1, region2):
        return region1 | region2

    def op_and(self, region1, region2):
        return region1 & region2

    def op_not(self, region1):
        #print ~region1
        return ~region1

    def op_sub(self, region1, region2):
        return region1 & (~region2)




class RegionFactoryDelegatedCreateShape(object):

    class IncorrectShape(Exception):
        pass

    class UnknownShape(Exception):
        pass

    def default_createshape(self, shape_name):
        raise self.UnknownShape("No information to create shape '%s'" % (shape_name))

    def create_shape(self, shape_name, number_list):

        number_list = map(float, number_list)
        
        try:
            meth = getattr(self, "shape_" + shape_name)
        except AttributeError:
            #raise self.UnknownShape("No information to create shape '%s'" % (shape_name))
            r = self.default_createshape(shape_name)
        else:
            r = meth(number_list)

        return r
            



class RegionFactorySimple(RegionFactoryDelegatedCreateShape, RegionFactoryPlainList):
    def shape_circle(self, number_list):
        return [("circle", number_list)]

    def shape_polygon(self, number_list):
        return [("polygon", number_list)]

    def shape_ellipse(self, number_list):
        return [("ellipse", number_list)]
        
    def shape_rotbox(self, number_list):
        return [("rotbox", number_list)]






        

class RegionParser(object):

    def commaed_list(cls, kl):
        if kl[-1] == "...":
            k0 = kl[0]
            for k in kl[1:-1]:
                k0 = k0 + "," + k
            k0 = k0 + ZeroOrMore("," + k0)
        else:
            k0 = kl[0]
            for k in kl[1:]:
                k0 = k0 + "," + k
        
        return k0.setParseAction(lambda s,l,t: t[::2])

    commaed_list = classmethod(commaed_list)
    
            
    def paren_comma(cls, literal_name):
        lparen = Literal("(").suppress()
        rparen = Literal(")").suppress()
        
        def _f(*kl):
            r = CaselessKeyword(literal_name) +  lparen + cls.commaed_list(kl) + rparen
            return r.setParseAction(lambda s,l,t:t[0])
        return _f

    paren_comma = classmethod(paren_comma)


    def simple_number(self):
        # fnumber : 102.43, 12304.3e10,
        #           .32???
        point = Literal( "." )
        e     = CaselessLiteral( "E" )
        fnumber = Combine( Word( "+-"+nums, nums ) + 
                           Optional( point + Optional( Word( nums ) ) ) +
                           Optional( e + Word( "+-"+nums, nums ) ) )
        #fnumber = _fnumber.setParseAction( lambda s,l,t: [ float(t[0]) ] )
        return fnumber

    #fnumber = _fnumber()

    def sexagesimal_part(self):
        # fnumber : 10:32:42.43, -32:32:54.3
        #           .32???

        point = Literal( "." )
        unsigend_float = Combine( Word(nums) + 
                                   Optional( point +
                                             Optional( Word( nums ) ) ) )

        _s = Combine(":" + unsigend_float + Optional(":" + unsigend_float))

        #s = _s.setParseAction( lambda s,l,t: [ sexagecimal(t[0]) ] )
        s = _s
        return s


    def sph_angle_part(self):
        # fnumber : 10' 32"

        point = Literal( "." )
        unsigend_float = Combine( Word(nums) + 
                                   Optional( point +
                                             Optional( Word( nums ) ) ) )

        _sec = Literal('"')
        #_sec.setParseAction(lambda s,l,t: float(t[0])/3600.)

        _min = Literal("'")
        #_min.setParseAction(lambda s,l,t: float(t[0])/60.)
        
        _s =  _sec | (_min + Optional(unsigend_float + _sec))

        #s = _s.setParseAction( lambda s,l,t: [ spherical_angle(sum(t[:])) ] )
        s = _s
        
        return s
    

    def pushShape(self, s, l, tok):
        #print "shape", tok
        s = self.region_factory.create_shape(tok[0], tok[1:])
        #print s
        self.stack.append(s)

    def pushParen(self, s, l, tok):
        pass
    #self.stack[-1] = "(%s)" % self.stack[-1]

    #def pushUMinus(self, s, l, tok):
    #    if tok[0] == "-":
    #        self.stack[-1] = "!" + self.stack[-1]


    def pushOr(self, s, l, tok):
        assert len(self.stack) >= 2
        r1, r2 = self.stack.pop(-2), self.stack.pop()
        r = self.region_factory.op_or(r1, r2)
        self.stack.append(r)

    def pushAnd(self, s, l, tok):
        assert len(self.stack) >= 2
        assert len(self.stack) >= 2
        r1, r2 = self.stack.pop(-2), self.stack.pop()
        r = self.region_factory.op_and(r1, r2)
        self.stack.append(r)

    def pushSub(self, s, l, tok):
        assert len(self.stack) >= 2
        r1, r2 = self.stack.pop(-2), self.stack.pop()
        r = self.region_factory.op_sub(r1, r2)
        self.stack.append(r)

    def do_comment(self, s, l, tok):
        if self.stack:
            self.comment_list.append((self.stack[-1], tok[1]))
        else:
            self.comment_list.append((None, tok[1]))

    def do_global(self, s, l, tok):
        self.global_list.append(tok[1])

    def do_coord(self, s, l, tok):
        pass

    #def define_shape(self, shape_name, *kl):
    #    _literal = self.paren_comma(shape_name)
    #    _shape = _literal(*kl)
    #    return _shape
        
    def __init__(self, region_factory):

        self.stack = []
        self.comment_list = []
        self.global_list = []
        self.region_factory = region_factory
        
        shape = self.define_shape().setParseAction(self.pushShape)

        self.comment = (Literal("#") + restOfLine).setParseAction(self.do_comment)

        self.ds9_global = (CaselessKeyword("global") + restOfLine).setParseAction(self.do_global)

        self.ds9_coord = (CaselessKeyword("image") | 
                          CaselessKeyword("physical") | 
                          CaselessKeyword("detector") | 
                          CaselessKeyword("fk4") | 
                          CaselessKeyword("icrs") |
                          CaselessKeyword("galactic") |
                          CaselessKeyword("ecliptic")) \
                          + Optional(";").setParseAction(self.do_coord)

        regionExpr = self.define_expr(shape)
        
        self.parser = regionExpr + StringEnd()
        self.parser.ignore(self.comment)
        self.parser.ignore(self.ds9_global)
        self.parser.ignore(self.ds9_coord)


    def define_number(self):
        return Combine(self.simple_number() + Optional(self.sexagesimal_part() | self.sph_angle_part()))
        
    def define_shape(self):

        number = self.define_number()

        lparen = Literal("(").suppress()
        rparen = Literal(")").suppress()
        comma = Literal(",").suppress()

        shape_name = Word( alphas, alphanums )
        

        shape = shape_name + lparen + number + ZeroOrMore(comma + number) + rparen

        return shape


    def define_expr(self, shape):

        comment = self.comment
        ds9_global = self.ds9_global

        lparen = Literal("(").suppress()
        rparen = Literal(")").suppress()

        regionExpr = Forward()
        regionParen = (lparen + regionExpr + rparen).setParseAction(self.pushParen)
        regionAtom = (shape | regionParen)
        #regionAtom = (shape | regionParen) + Optional(comment)
        #regionAtom = (shape | regionParen) + Optional(comment | ds9_global)
        regionOr = (Optional(CaselessKeyword("or")) + regionAtom).setParseAction(self.pushOr)
        regionAnd = (CaselessKeyword("and") + regionAtom).setParseAction(self.pushAnd)
        regionSub = ("-" + regionAtom).setParseAction(self.pushSub)
        regionExpr <<  (regionAtom) + ZeroOrMore(regionOr | regionAnd | regionSub)

        return regionExpr

    
        
    def parseString(self, s):
        self.parser.parseString(s)
        assert len(self.stack) == 1
        return self.stack.pop()


#def test():
#if 0:
    
    #a = rr.parseString("circle(30,40,30) a(3)")
#test()



if 0:
    class ttt(object):
        def shape_circle(coord_x, coord_y, radius):
            print coord_x

    a = ttt()
